﻿namespace Summoner
{
#if UNITY_EDITOR
    using UnityEditor;
#endif
    using UnityEngine;
    using System;
    using System.Collections.Generic;
    using Object = UnityEngine.Object;
    using System.IO;
    public class AssetObtainer : IDisposable
    {
        private static AssetObtainer _instance = null;
        private Queue<LoadMission> _loadqueue = new Queue<LoadMission>();
        private LoadMission _curmission = null;
        private Dictionary<string, AssetInfo> _name2info = new Dictionary<string, AssetInfo>();
        private LRUDictionary<string, LoadedAssetRef> _lruRef = new LRUDictionary<string, LoadedAssetRef>();
        private GameConfigInfo _gameInfo = null;
#if UNITY_EDITOR
        private AssetVessel _vessel = null;
#endif
        private bool _waitting = false;
        private Action _onInit = null;

        public AssetObtainer()
        {
            _instance = this;
        }

        public void Init(Action callback)
        {
            this._onInit = callback;
            if (AssetPath.IsEditor)
            {
#if UNITY_EDITOR
                this.InitInEditor();
#endif
            }
            else
            {
                LoadFunction.instance.Init(this.OnManifestLoaded);
            }
        }

        public void Init()
        {
            if (AssetPath.IsEditor)
            {
#if UNITY_EDITOR
                this.InitInEditor();
#endif
            }
            else
            {
                LoadFunction.instance.Init();
            }
        }

        private void InitInEditor()
        {
            this._vessel = AssetDatabase.LoadAssetAtPath<AssetVessel>(AssetPath.AssetVesselPath);
            if (this._vessel != null)
            {
                foreach (AssetInfo info in this._vessel.assetList)
                {
                    if (!this._name2info.ContainsKey(info.Name))
                    {
                        this._name2info[info.Name] = info;
                    }
                }
            }
        }

        private void OnManifestLoaded()
        {
            this.LoadConfig(AssetPath.AssetConfigName.ToLower(), this.OnAssetInfosLoaded);
        }
          
        private void OnAssetInfosLoaded(string str)
        {
            this._gameInfo = JsonSerializer.UnpackJson<GameConfigInfo>(str);
            if(this._gameInfo != null)
            {
                foreach(AssetInfo info in this._gameInfo.assets)
                {
                    if (!this._name2info.ContainsKey(info.Name))
                    {
                        this._name2info[info.Name] = info;
                    }
                }
                if (this._onInit != null) { this._onInit(); }
            }
        }

        public void LoadConfigFullpath(string path, Action<string> callback)
        {
            LoadFunction.instance.LoadConfig(path, callback);
        }

        public void LoadConfig(string filename, Action<string> callback, string ext = ".json")
        {
            if (AssetPath.IsEditor)
            {
                if (this._name2info.Count == 0) { this.Init(); }
                TextAsset ta = (TextAsset)this._name2info[filename].asset;
                if (ta != null) { if (callback != null) { callback(ta.text); } }
                else { if (callback != null) { callback(""); } }
            }
            else
            {
                string url = this.GetConfigLoadPath(filename, ext);
                LoadFunction.instance.LoadConfig(url, callback);
            }
        }

        private string GetConfigLoadPath(string filename, string ext)
        {
            if(File.Exists(Path.Combine(AssetPath.DocumentsPath, filename + ext)))
            {
                return Path.Combine(AssetPath.DocumentsPath, filename + ext);
            }
            else
            {
                return Path.Combine(AssetPath.LoadAsyncPath, filename + ext);
            }
        }

        public void LoadAssetAsync(string name, Action<object> callback, object parameter = null)
        {
            if (this._name2info.Count == 0) { this.Init(); }
            this._loadqueue.Enqueue(new LoadMission() { name = name, assetname = this._name2info[name].assetName, type = this._name2info[name].type, callback = callback, parameter = parameter });
            this.CheckLoadList();
        }

        private void DoLoadAsset(LoadMission mission, Action<object> callback)
        {
            if (AssetPath.IsEditor)
            {
                if (callback != null)
                {
                    if(this._name2info[mission.name].type == "UnityEngine.GameObject")
                    {
                        callback((GameObject)Object.Instantiate(this._name2info[mission.name].asset));
                    }
                    else
                    {
                        callback(this._name2info[mission.name].asset);
                    }
                }
            }
            else
            {
                if (AssetPath.UseResources)
                {
                    if (callback != null)
                    {
                        if (this._name2info[mission.name].type == "UnityEngine.GameObject")
                        {
                            callback(Object.Instantiate(Resources.Load(mission.assetname)));
                        }
                        else
                        {
                            callback(Resources.Load(mission.assetname));
                        }
                    }
                }
                else
                {
                    LoadFunction.instance.LoadBundleAsync(mission, this.OnAssetLoaded);
                }
            }
        }

        private void OnAssetLoaded(object obj)
        {
            if(!this._lruRef.ContainsKey(this._curmission.name))
            {
                this._lruRef[this._curmission.name] = new LoadedAssetRef() { name = (obj as Object).name, count = 1, size = this._name2info[this._curmission.name].RamSize };
            }
            else
            {
                this._lruRef[this._curmission.name].count += 1;
                this.ChangeLRNOrder(this._curmission.name);
            }
            object temp = null;
            if (this._curmission.type == "UnityEngine.GameObject")
            {
                temp = (GameObject)obj;
            }
            else
            {
                temp = obj;
            }
            if (this._curmission.parameter == null)
            {
                this._curmission.callback(temp);
            }
            else
            {
                this._curmission.callback(new LoadParam() { obj = temp, parameter = this._curmission.parameter });
            }
            if (!AssetPath.IsEditor && !AssetPath.UseResources)
            {
                LoadFunction.instance.UnloadAsync(this._curmission.assetname);
            }
            this._waitting = false;
            this.CheckLoadList();
        }

        public Object LoadAssetSync(string name)
        {
            if (this._name2info.Count == 0) { this.Init(); }
            if (AssetPath.IsEditor)
            {
                if (this._name2info[name].type == "UnityEngine.GameObject")
                {
                    return Object.Instantiate((GameObject)this._name2info[name].asset);
                }
                else
                {
                    return this._name2info[name].asset;
                }
            }
            else
            {
                if (AssetPath.UseResources)
                {
                    if (this._name2info[name].type == "UnityEngine.GameObject")
                    {
                        GameObject go = (GameObject)Object.Instantiate(Resources.Load(this._name2info[name].assetName));
                        return go;
                    }
                    else
                    {
                        return Resources.Load(this._name2info[name].assetName);
                    }
                }
                else
                {
                    AssetBundle bundle = LoadFunction.instance.LoadBundleSync(this._name2info[name].assetName);
                    if (bundle != null)
                    {
                        Object obj = null;
                        if (this._name2info[name].type == "UnityEngine.GameObject")
                        {
                            obj = (GameObject)Object.Instantiate(bundle.LoadAsset(this._name2info[name].Name));
                        }
                        else
                        {
                            obj = bundle.LoadAsset(this._name2info[name].Name);
                        }
                        LoadFunction.instance.UnloadSync(this._name2info[name].assetName);
                        return obj;
                    }
                }
            }
            return null;
        }

        public void LoadImageAsync(string url, Action<Texture2D> callback)
        {
            if (this._name2info.Count == 0) { this.Init(); }
            LoadFunction.instance.LoadImageAsync(url, callback);
        }

        public void LoadSpriteFromImageAsync(string url, Action<Sprite> callback)
        {
            if (this._name2info.Count == 0) { this.Init(); }
            LoadFunction.instance.LoadImageAsync(url, (texture)=>
            {
                if(texture != null)
                {
                    Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.one / 2);
                    if (callback != null) { callback(sprite); }
                }
            });
        }

#if UNITY_EDITOR
        public Object GetPrefabRef(string name)
        {
            if (this._vessel == null) { this._vessel = AssetDatabase.LoadAssetAtPath<AssetVessel>(AssetPath.AssetVesselPath); }
            if (this._name2info.Count > 0) { this._name2info.Clear(); }
            foreach (AssetInfo info in this._vessel.assetList)
            {
                if (!this._name2info.ContainsKey(info.Name)) { this._name2info[info.Name] = info; }
            }
            return this._name2info[name].asset;
        }
#endif

        private void ChangeLRNOrder(string name) { }

        private string FilterAssetName(string str)
        {
            int index = str.IndexOf("-");
            string name = str;
            if(index > 0)
            {
                name = str.Substring(0, index);
            }
            index = 0;
            index = name.IndexOf("(Clone)");
            if(index > 0)
            {
                name = name.Substring(0, index);
            }
            return name;
        }

        public void DeleteAsset(Object obj)
        {
            string name = this.FilterAssetName(obj.name);
            if (this._lruRef.ContainsKey(name))
            {
                this._lruRef[name].count -= 1;
                if (this._lruRef[name].count == 0) { this._lruRef.Remove(name); }
                if(obj is GameObject)
                {
                    Object.Destroy(obj);
                }
                else
                {
                    if(!AssetPath.IsEditor)
                    {
                        Object.DestroyImmediate(obj, true);
                    }
                }
            }
            else
            {
                if (obj is GameObject)
                {
                    Object.Destroy(obj);
                }
                else
                {
                    if (!AssetPath.IsEditor)
                    {
                        Object.DestroyImmediate(obj, true);
                    }
                }
            }
        }

        private void CheckLoadList()
        {
            if (!this._waitting)
            {
                if (this._loadqueue.Count > 0)
                {
                    this._waitting = true;
                    this._curmission = this._loadqueue.Dequeue();
                    this.DoLoadAsset(this._curmission, this.OnAssetLoaded);
                }
            }
        }

        public void End() { }

        public static AssetObtainer instance
        {
            get
            {
                if (_instance == null) { _instance = new AssetObtainer(); }
                return _instance;
            }
        }

        #region IDisposable Support
        private bool disposedValue = false; // 要检测冗余调用

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    this.End();
                }
                disposedValue = true;
            }
        }

        // 添加此代码以正确实现可处置模式。
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }

    public class LoadMission
    {
        public string name;
        public string assetname;
        public string type;
        public Action<object> callback;
        public object parameter;
    }

    public class LoadParam
    {
        public object obj;
        public object parameter;
    }

    public class LoadedAssetRef
    {
        public string name;
        public int count;
        public int size;
    }
}